﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Pipes;
using Main.PrimitivesCommunication;
using System.IO;
using System.Threading;
using Main.Packets;
using Core.Connexions;

namespace Main.Reseau
{
    /// <summary>
    /// Défini les opérations de la couche réseau
    /// </summary>
    class CoucheReseau
    {
        public NamedPipeServerStream pipeReseauToTransport;
        public NamedPipeClientStream pipeTransportToReseau;

        Queue<Packet> packetsFromLiaisonToReseau = new Queue<Packet>();
        Queue<Primitive> primitivesFromLiaisonToReseau = new Queue<Primitive>();
        Queue<Primitive> primitivesReadFromTransportToReseau = new Queue<Primitive>();

        bool waitingConnection = true;

        /// <summary>
        /// Méthode exécutée par un thread. Elle s'occupe de lire les primitives venant de la couche transport
        /// et de les ajouter à la file de primitives lues.
        /// </summary>
        public void LireDeTransport()
        {
            pipeTransportToReseau = new NamedPipeClientStream(REGLES.PIPE_TRANSPORT_TO_RESEAU);
            StreamReader streamReaderTransportToReseau = new StreamReader(pipeTransportToReseau);
            pipeTransportToReseau.Connect();
            Console.WriteLine("Lire de Tranport : Connecte au pipe TRANSPORT_TO_RESEAU");

            Primitive primitiveLue;
            while (true)
            {
                primitiveLue = Primitive.Receive(streamReaderTransportToReseau);

                if (primitiveLue != null)
                {
                    Console.WriteLine("Primitive recue : " + primitiveLue.ToString());
                    
                    primitivesReadFromTransportToReseau.Enqueue(primitiveLue);
                    waitingConnection = false;
                }
            }
        }

        /// <summary>
        /// Méthode exécutée par le processus principal. Elle s'occupe de traiter les primitives lues et de simuler 
        /// l'envoi vers la couche liaison et le retour qui s'en suit.
        /// </summary>
        public void TraiterPrimitivesDeTransport()
        {
            Packet packetToSendToLiaison = null;
            Packet packetReponseFromLiaisonToReseau = null;
            Primitive primitiveRecu;
            Primitive primitiveReponse;
            ConnexionReseau connexionReseau;

            while (waitingConnection)
                Thread.Sleep(50);

            while (true)
            {
                if (primitivesReadFromTransportToReseau.Count > 0)
                {
                    //On va chercher une primitive dans la file
                    primitiveRecu = primitivesReadFromTransportToReseau.Dequeue();

                    primitiveReponse = null;
                    packetReponseFromLiaisonToReseau = null;

                    //On construit le paquet à être envoyé à la couche liaison selon le type de primitive et on simule une réponse
                    switch (primitiveRecu.PrimitiveType)
                    {
                        case PrimitiveTypes.NConnectRequest:
                            if (ConnexionAcceptee(primitiveRecu.AdresseSource))
                            {
                                connexionReseau = ConnexionReseau.GetConnexion(primitiveRecu.IdConnexion, primitiveRecu.AdresseSource, primitiveRecu.AdresseDestination);
                                packetToSendToLiaison = ConnectionPacket.CreateRequestConnectionPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);

                                //Simulation de l'envoie vers la couche liaison et eventuellement vers le systeme B et paquet recu de liaison(Reponse du System B)
                                packetReponseFromLiaisonToReseau = connexionReseau.EnvoieVersLiaison(packetToSendToLiaison);
                                Console.WriteLine("Paquet envoye : " + packetToSendToLiaison + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                            }
                            else
                            {
                                primitiveReponse = Primitive.CreateNDisconnectInd(primitiveRecu.IdConnexion, Raisons.FournisseurRefuse);

                                if (primitiveReponse != null)
                                    primitivesFromLiaisonToReseau.Enqueue(primitiveReponse);
                            }
                            break;

                        case PrimitiveTypes.NDataRequest:

                            connexionReseau = ConnexionReseau.GetConnexionFromTransport(primitiveRecu.IdConnexion);

                            if (connexionReseau != null)
                            {
                                DataPacket.CreateDataPackets(connexionReseau, primitiveRecu.DonneesUtilisateur);

                                do
                                {
                                    DataPacket firstDataPacket = connexionReseau.GetFirstDataPacketWaiting();
                                    packetReponseFromLiaisonToReseau = connexionReseau.EnvoieVersLiaison(firstDataPacket);
                                    Console.WriteLine("Paquet envoye : " + firstDataPacket + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                                }
                                while (connexionReseau.PretATransmettre);

                                if (packetReponseFromLiaisonToReseau != null && packetReponseFromLiaisonToReseau.PacketType.Type == (PacketTypes.NegativeAcknowledgmentPacket))
                                {
                                    if (!(connexionReseau.Etat == EtatsConnexion.EnDeconnexion))
                                    {
                                        //Simulation de l'envoie vers la couche liaison
                                        FreeConnectionPacket freeConnectionPacket = FreeConnectionPacket.CreateFreeConnectionRequestPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);
                                        connexionReseau.EnvoieVersLiaison(freeConnectionPacket);
                                        Console.WriteLine("Paquet envoye : " + freeConnectionPacket);
                                        connexionReseau.Etat = EtatsConnexion.EnDeconnexion;
                                    }
                                }
                            }
                            break;

                        case PrimitiveTypes.NDisconnectRequest:
                            connexionReseau = ConnexionReseau.GetConnexionFromTransport(primitiveRecu.AdresseReponse);
                            if (connexionReseau != null)
                            {
                                if (!(connexionReseau.Etat == EtatsConnexion.EnDeconnexion))
                                {
                                    packetToSendToLiaison = FreeConnectionPacket.CreateFreeConnectionRequestPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);
                                    connexionReseau.Etat = EtatsConnexion.EnDeconnexion;
                                    //Simulation de l'envoie vers la couche liaison et eventuellement vers le systeme B et paquet recu de liaison(Reponse du System B)
                                    packetReponseFromLiaisonToReseau = connexionReseau.EnvoieVersLiaison(packetToSendToLiaison);
                                    Console.WriteLine("Paquet envoye : " + packetToSendToLiaison + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                                }
                            }
                            break;
                    }

                    //Si liaison nous envoi une réponse on l'ajoute à la file pour qu'elle soit traitée
                    if (packetReponseFromLiaisonToReseau != null)
                    {
                        packetsFromLiaisonToReseau.Enqueue(packetReponseFromLiaisonToReseau);
                        packetReponseFromLiaisonToReseau = null;
                    }
                }

                TraiterTemporisateurs();

                //On termine l'exécution de la boucle s'il n'y a plus de connexions
                if (ConnexionReseau.ListeConnexions.Count == 0)
                    break;
            }
        }

        /// <summary>
        /// Méthode s'occupant de faire la gestion de la retransmission en cas de temps de temporisateur dépassé
        /// </summary>
        public void TraiterTemporisateurs()
        {
            Packet packetReponseFromLiaisonToReseau = null;
            List<ConnexionReseau> listeConnexions = new List<ConnexionReseau>();

            //Création d'une liste statique de connexions
            foreach (ConnexionReseau cr in ConnexionReseau.ListeConnexions)
            {
                if (cr != null)
                    listeConnexions.Add(cr);
            }

            foreach (ConnexionReseau connexionReseau in listeConnexions)
            {
                if (connexionReseau.TimerElapsed)
                {
                    if (connexionReseau.Etat == EtatsConnexion.AttenteConfirmation)
                    {
                        ConnectionPacket connectionPacket = ConnectionPacket.CreateRequestConnectionPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);

                        //Simulation de l'envoie vers la couche liaison et eventuellement vers le systeme B et paquet recu de liaison(Reponse du System B)
                        packetReponseFromLiaisonToReseau = connexionReseau.EnvoieVersLiaison(connectionPacket);
                        Console.WriteLine("Paquet envoye : " + connectionPacket + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);

                        //Si on ne reçoit pas de réponse et que la connexiopn n'est pas déjà en déconnexion, on déconnecte
                        if (packetReponseFromLiaisonToReseau == null && !(connexionReseau.Etat == EtatsConnexion.EnDeconnexion))
                        {
                            FreeConnectionPacket freeConnectionPacket = FreeConnectionPacket.CreateFreeConnectionRequestPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);
                            connexionReseau.EnvoieVersLiaison(freeConnectionPacket);
                            packetReponseFromLiaisonToReseau = FreeConnectionPacket.CreateFreeConnectionIndicationPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination, Raisons.FournisseurRefuse);

                            Console.WriteLine("Paquet envoye : " + freeConnectionPacket + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                            connexionReseau.Etat = EtatsConnexion.EnDeconnexion;
                        }
                    }
                    else if (connexionReseau.PretATransmettre)
                    {
                        do
                        {
                            DataPacket firstDataPacket = connexionReseau.GetFirstDataPacketWaiting();
                            packetReponseFromLiaisonToReseau = connexionReseau.EnvoieVersLiaison(firstDataPacket);
                            Console.WriteLine("Paquet envoye : " + firstDataPacket + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                        }
                        while (connexionReseau.PretATransmettre);
                    }
                    else if (connexionReseau.Etat == EtatsConnexion.ConnexionEtablie)
                    {
                        FreeConnectionPacket freeConnectionPacket = FreeConnectionPacket.CreateFreeConnectionRequestPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination);
                        connexionReseau.EnvoieVersLiaison(freeConnectionPacket);
                        packetReponseFromLiaisonToReseau = FreeConnectionPacket.CreateFreeConnectionIndicationPacket(connexionReseau.IdConnexionLogique, connexionReseau.AdresseSource, connexionReseau.AdresseDestination, Raisons.FournisseurRefuse);

                        Console.WriteLine("Paquet envoye : " + freeConnectionPacket + "\n\tPaquet recu : " + packetReponseFromLiaisonToReseau);
                        connexionReseau.Etat = EtatsConnexion.EnDeconnexion;
                    }
                }

                //On ajoute la réponse venant de liaison à la liste de réponses à traiter
                if (packetReponseFromLiaisonToReseau != null)
                    packetsFromLiaisonToReseau.Enqueue(packetReponseFromLiaisonToReseau);
            }
        }

        /// <summary>
        /// Méthode exécutée par un thread s'occupant d'envoyer les réponses de liaison à la couche transport sous 
        /// forme de primitives
        /// </summary>
        public void EcrireVersTransport()
        {
            pipeReseauToTransport = new NamedPipeServerStream(REGLES.PIPE_RESEAU_TO_TRANSPORT, PipeDirection.InOut, 10, PipeTransmissionMode.Message);
            StreamWriter streamWriterReseauToTransport = new StreamWriter(pipeReseauToTransport);

            Console.WriteLine("Attente de connexion sur le pipe RESEAU_TO_TRANSPORT");
            pipeReseauToTransport.WaitForConnection();
            streamWriterReseauToTransport.AutoFlush = true;

            Packet packet;
            while (true)
            {
                Thread.Sleep(50);
                Primitive primitiveReponse = null;
                ConnexionReseau connexionReseau;

                if (packetsFromLiaisonToReseau.Count > 0)
                {
                    //On va chercher les paquets de réponse de liaison
                    packet = packetsFromLiaisonToReseau.Dequeue();
                    connexionReseau = ConnexionReseau.GetConnexionFromReseau(packet.NumeroConnexion);

                    if (connexionReseau != null)
                        //On construit la primitive à envoyer selon le type de paquet
                        switch (packet.PacketType.Type)
                        {
                            case PacketTypes.ConnectionConfirmationPacket:

                                primitiveReponse = Primitive.CreateNConnectConf(connexionReseau.IdConnexionTransport);
                                connexionReseau.Etat = EtatsConnexion.ConnexionEtablie;
                                break;
                            case PacketTypes.FreeConnectionPacket:
                                FreeConnectionPacket freePacket = (FreeConnectionPacket)packet;
                                primitiveReponse = Primitive.CreateNDisconnectInd(connexionReseau.IdConnexionTransport, freePacket.Raison);
                                connexionReseau.Destroy();
                                break;
                            case PacketTypes.NegativeAcknowledgmentPacket:
                                AcknowledgementPacket ackPacket = (AcknowledgementPacket)packet;

                                primitiveReponse = Primitive.CreateNDisconnectInd(connexionReseau.IdConnexionTransport, Raisons.FournisseurRefuse);
                                connexionReseau.Destroy();
                                break;
                            default:
                                throw new NotImplementedException();
                        }
                }
                else if (primitivesFromLiaisonToReseau.Count > 0)
                {
                    primitiveReponse = primitivesFromLiaisonToReseau.Dequeue();
                }

                //On envoi la primitive vers la couche transport
                if (primitiveReponse != null)
                {
                    primitiveReponse.Send(streamWriterReseauToTransport);
                    Console.WriteLine("Primitive envoyee : " + primitiveReponse.ToString());
                }
            }
        }

        /// <summary>
        /// Retourne si la connexion est acceptée pour une adresse source
        /// </summary>
        private bool ConnexionAcceptee(byte adresseSource)
        {
            return !((adresseSource % REGLES.MULTIPLE_POUR_REFUS_CONNEXION_FOURNISSEUR) == 0);
        }
    }
}
